﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BruteForceSerializer.Contract.Internal;
using BruteForceSerializer.Normalization;
using BruteForceSerializer.Tests.TestClasses;
using Microsoft.VisualStudio.TestTools.UnitTesting;


namespace BruteForceSerializer.SL.Tests
{
	[TestClass]
	public class test_Normalizer
	{
		private Normalizer normalizer;

		[TestInitialize]
		public void SetUp()
		{
			normalizer = new Normalizer();
		}

		[TestMethod]
		public void Null_Root_bleibt_null()
		{
			var no = normalizer.Normalize(null);
			Assert.IsNull(no);
			var obj = normalizer.DeNormalize(null);
			Assert.IsNull(obj);

		}


		[TestMethod]
		public void Normalisiertes_Objekt_mit_Typname_des_Originals()
		{
			var no = normalizer.Normalize(new KlasseOhneFelder());
			Assert.AreEqual(typeof(KlasseOhneFelder).FullName, no.Typename);
			var obj = normalizer.DeNormalize(no);
			Assert.IsInstanceOfType(obj, typeof(KlasseOhneFelder));
		}


		[TestMethod]
		public void Objekt_ohne_Felder()
		{
			var no = normalizer.Normalize(new KlasseOhneFelder());
			Assert.AreEqual(0, no.Fields.Count());
		}

		[TestMethod]
		public void Objekt_mit_skalaren_Feldern()
		{
			var no = normalizer.Normalize(new KlasseMitSkalarenFeldern());
			Assert.IsTrue(no.Fields.Select(f => f.Name).ToArray().ArrayEquals(
						new[]
                            {
                                "intField",
                                "byteField",
                                "stringField",
                                "doubleField",
                                "floatField",
                                "decimalField",
                                "dateTimeField",
                                "enumField"
                            }));

			Assert.IsTrue(no.Fields.Select(f => f.Value).ToArray().ArrayEquals(
						new object[]
                            {
                                42,
								(byte)1,
                                "hello",
                                3.14,
                                3.14f,
                                decimal.Parse("3.14"),
                                new DateTime(2010, 5, 12, 12, 11, 10),
                                Farben.grün,
                            }));
			var obj = normalizer.DeNormalize(no);
		}

		[TestMethod]
		public void Objekt_mit_skalaren_Feldern_als_object()
		{
			var start = new KlasseMitSkalarenFeldernAlsObj();
			var no = normalizer.Normalize(start);
			var result = (KlasseMitSkalarenFeldernAlsObj)normalizer.DeNormalize(no);
			Assert.AreEqual(start.byteField, result.byteField);
			Assert.AreEqual(start.dateTimeField, result.dateTimeField);
			Assert.AreEqual(start.decimalField, result.decimalField);
			Assert.AreEqual(start.doubleField, result.doubleField);
			Assert.AreEqual(start.enumField, result.enumField);
			Assert.AreEqual(start.floatField, result.floatField);
			Assert.AreEqual(start.intField, result.intField);
			Assert.AreEqual(start.stringField, result.stringField);
		}

		[TestMethod]
		public void Null_Referenz_in_Feld()
		{
			var o = new KlasseMitSkalarenFeldern();
			o.stringField = null;

			var no = normalizer.Normalize(o);

			Assert.IsNull(no.Fields.Where(f => f.Name == "stringField").Select(f => f.Value).First());
		}

		[TestMethod]
		public void Objekt_mit_nicht_öffentlichen_Feldern()
		{
			var no = normalizer.Normalize(new KlasseMitNichtOeffentlichenFeldern());
			Assert.AreEqual(0, no.Fields.Count());
			//Assert.That(no.Fields.Select(f => f.Name).ToArray(), Is.EqualTo(
			//            new[]
			//                {
			//                    "privateField",
			//                    "protectedField",
			//                    "internalField",
			//                }));

			//Assert.That(no.Fields.Select(f => f.Value).ToArray(), Is.EqualTo(
			//            new object[]
			//                {
			//                    42,
			//                    "hello",
			//                    3.14
			//                }));
			var obj = normalizer.DeNormalize(no);
		}

		//[TestMethod]
		//public void Objekt_mit_nicht_serialisierbarem_Feld()
		//{
		//    var no = normalizer.Normalize(new KlasseMitNichtSerialisierbaremFeld());
		//    Assert.AreEqual(0, no.Fields.Count());
		//    var obj = normalizer.DeNormalize(no);
		//}


		[TestMethod]
		public void Geschachtelte_Objekte()
		{
			var no = normalizer.Normalize(new Parent());
			//Assert.IsInstanceOf<INormalizedObject>(no.Fields.First().Value);
			Assert.AreEqual(typeof(Child).FullName, ((INormalizedObject)no.Fields.First().Value).Typename);
			var obj = normalizer.DeNormalize(no);
		}

		[TestMethod]
		public void Referenz_auf_geschachteltes_Object_ist_Null()
		{
			var p = new Parent();
			p.child = null;

			var no = normalizer.Normalize(p);

			Assert.IsNull(no.Fields.First().Value);

			var obj = normalizer.DeNormalize(no);
			Assert.IsNull(((Parent)obj).child);
		}

		[TestMethod]
		public void Objekte_haben_IDs()
		{
			var no = normalizer.Normalize(new Parent());

			Assert.AreEqual(0, no.Id);
			Assert.AreEqual(1, ((INormalizedObject)no.Fields.First().Value).Id);
		}

		[TestMethod]
		public void Direkte_zirkuläre_Abhängigkeit()
		{
			var rootChild = new KlasseMitZirkulaererAbhaengigkeit();
			rootChild.Next = rootChild;

			var no = normalizer.Normalize(rootChild);

			var reference = (INormalizedReference)no.Fields.First().Value;
			Assert.AreEqual(0, reference.ObjectId);
		}

		[TestMethod]
		public void Indirekte_zirkuläre_Abhängigkeit()
		{
			var root = new KlasseMitZirkulaererAbhaengigkeit();
			var child = new KlasseMitZirkulaererAbhaengigkeit();
			var grandchild = new KlasseMitZirkulaererAbhaengigkeit();
			root.Next = child;
			child.Next = grandchild;
			grandchild.Next = child;

			var no = normalizer.Normalize(root);

			var normalizedChild = (INormalizedObject)no.Fields.First().Value;
			var normalizedGrandchild = (INormalizedObject)normalizedChild.Fields.First().Value;
			var refChild = (INormalizedReference)normalizedGrandchild.Fields.First().Value;
			Assert.AreEqual(1, refChild.ObjectId);

			var obj = normalizer.DeNormalize(no);
		}

		[TestMethod, Ignore]
		public void Objekt_mit_Delegaten()
		{
			Action action = () => { };

			var no = normalizer.Normalize(action);

			//NOTE: nicht sehr erhellend, aber funktioniert
			foreach (var f in no.Fields)
				Console.WriteLine("{0}={1}", f.Name, f.Value);
		}

		[TestMethod]
		public void Arrayelemente_als_Felder_interpretieren()
		{
			var no = normalizer.Normalize(new[] { 1, 2 });

			Assert.IsTrue(no.Fields.Select(f => f.Name).ToArray().ArrayEquals(new[] { "0", "1" }));
			Assert.IsTrue(no.Fields.Select(f => f.Value).ToArray().ArrayEquals(new[] { 1, 2 }));
		}

		[TestMethod]
		public void Mehrdimensionales_Array()
		{
			var no = normalizer.Normalize(new[,] { { 1, 2 }, { 3, 4 } });

			Assert.IsTrue(no.Fields.Select(f => f.Name).ToArray().ArrayEquals(new[] { "0", "1", "2", "3" }));
			Assert.IsTrue(no.Fields.Select(f => f.Value).ToArray().ArrayEquals(new[] { 1, 2, 3, 4 }));
		}

		[TestMethod]
		public void Struct_wird_behandelt_wie_ein_Objekt()
		{
			StructStattKlasse rec;
			rec.intField = 42;

			var no = normalizer.Normalize(rec);

			Assert.AreEqual(42, no.Fields.First().Value);

			var obj = normalizer.DeNormalize(no);
		}

		[TestMethod]
		public void IntPtr_wie_Int_behandeln()
		{
			var o = new KlasseMitIntPtr();

			var no = normalizer.Normalize(o);

			//Assert.IsInstanceOf<Int64>(no.Fields.First().Value);
			Assert.AreEqual((long)0, no.Fields.First().Value);

			var obj = normalizer.DeNormalize(no);
		}

		[TestMethod]
		public void Fehlerreproduktion_Stringinhalt_wurde_nicht_in_Field_übersetzt_wenn_String_das_root_object_ist()
		{
			var no = normalizer.Normalize("hello");
			//Assert.IsInstanceOf<string>(no.Fields.First().Value);
			Assert.AreEqual("hello", no.Fields.First().Value);

			var obj = normalizer.DeNormalize(no);
			Assert.AreEqual("hello", obj.ToString());
		}

		[TestMethod]
		public void KlasseMitCollections()
		{
			var no = normalizer.Normalize(new KlasseMitCollections());

			var obj = normalizer.DeNormalize(no);
			Assert.AreEqual("eins", ((KlasseMitCollections)obj).Dict[1]);
		}

		[TestMethod]
		public void Surrogate()
		{
			normalizer.Surrogates.Add(new TestSurrogate());
			var no = normalizer.Normalize(new KlasseMitSkalarenFeldern());
			Assert.AreEqual("customizedField", no.Fields.First().Name);
			var obj = normalizer.DeNormalize(no);
			Assert.AreEqual(new KlasseMitSkalarenFeldern().intField * 4, ((KlasseMitSkalarenFeldern)obj).intField);
		}

		[TestMethod]
		public void KlasseMitTyp()
		{
			var no = normalizer.Normalize(new KlasseMitTyp());

			var obj = normalizer.DeNormalize(no);
			Assert.AreEqual(typeof(int), ((KlasseMitTyp)obj).IntType);
		}

		[TestMethod]
		public void Objekt_mit_Vererbung()
		{
			var no = normalizer.Normalize(new HierarchyChild());
			Assert.IsTrue(no.Fields.Select(f => f.Name).ToArray().ArrayEquals(
						new[]
			                {
			                    "ChildField",
                                "ParentField",
			                    "ChildProperty",
			                    "ParentProperty",
			                }));

			var obj = normalizer.DeNormalize(no);
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentException))]
		public void KlasseNichtPublic()
		{
			var no = normalizer.Normalize(new KlasseNichtPublic());
		}

		[TestMethod]
		public void ExceptionMitMsg()
		{
			var no = normalizer.Normalize(new ArgumentException("testmsg"));

			var obj = normalizer.DeNormalize(no);
			Assert.AreEqual(typeof(ArgumentException), obj.GetType());
			Assert.AreEqual("testmsg", ((Exception)obj).Message);
		}
	}
}